﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Practices.Prism.Commands;
using MvvmOrm.DbmsModules;
using MvvmOrm.Properties;
using MvvmOrm.Services;

namespace MvvmOrm.Models.DatabaseModel
{
    partial class Row : IDataErrorInfo
    {
        public static Row GetNew(DbSystemEnum dbSystem, string outputDirectory)
        {
            Row row = new Row();
            row.DbSystemEnum = dbSystem;
            row.MsAccessSecurityType = (int)MsaccessSecurityType.None;
            row.SqlServerUseIntegratedSecurity = true;
            row.Namespace = string.IsNullOrWhiteSpace(outputDirectory) ? "" : outputDirectory.Split(new[] { '\\' }).Last();
            row.OutputDirectory = outputDirectory;
            return row;
        }

        Dialogs Dialogs { get; set; }

        public DelegateCommand SetDatabaseFileCmd { get; private set; }
        public DelegateCommand SetSchemaFileCmd { get; private set; }
        public DelegateCommand SetOutputDirectoryCmd { get; private set; }
        public DelegateCommand AnalyseCmd { get; private set; }
        public DelegateCommand GenerateCmd { get; private set; }

        partial void PostInitialize()
        {
            Child_Logs_OrderBy = new List<Log.Param>();
            Child_Logs_OrderBy.Add(new Log.Param(Log.Field.Id));
            
            Dialogs = new Dialogs();

            SetDatabaseFileCmd = new DelegateCommand(SetDatabaseFile);
            SetSchemaFileCmd = new DelegateCommand(SetSchemaFile);
            SetOutputDirectoryCmd = new DelegateCommand(SetOutputDirectory);
            AnalyseCmd = new DelegateCommand(Analyse, CanAnalyse);
            GenerateCmd = new DelegateCommand(Generate, CanGenerate);
        }

        public string Error
        {
            get { throw new NotImplementedException(); }
        }

        public string this[string columnName]
        {
            get {
                string result = null;
                //switch (columnName) {
                //case "Foo":
                //    if (!Bar)
                //        result = "invalid Foo";
                //    break;
                //}
                return result;
            }
        }

        public IEnumerable<DbSystemEnum> DbSystemList
        {
            get
            {
                return Enum.GetValues(typeof(DbSystemEnum)).Cast<DbSystemEnum>();
            }
        }

        public DbSystemEnum DbSystemEnum
        {
            get { return (DbSystemEnum)DbSystem; }
            set
            {
                if (value == (DbSystemEnum)DbSystem)
                    return;
                DbSystem = (int)value;
                RaisePropertyChanged(() => DbSystemEnum);
                AnalyseCmd.RaiseCanExecuteChanged();
                GenerateCmd.RaiseCanExecuteChanged();
            }
        }

        void SetDatabaseFile()
        {
            string filename;
            if (Dialogs.OpenFile(out filename, null, "Database", null) != DialogResponse.Ok)
                return;
            Database = filename;
            UseSchemaFile = false;
        }

        void SetSchemaFile()
        {
            string filename;
            if (Dialogs.OpenFile(out filename, null, "Database Schema", null) != DialogResponse.Ok)
                return;
            SchemaFile = filename;
            UseSchemaFile = true;
        }

        void SetOutputDirectory()
        {
            string path;
            if (Dialogs.OpenDirectory(out path, "Select output directory", OutputDirectory, true) != DialogResponse.Ok)
                return;
            OutputDirectory = path;
        }

        void Analyse()
        {
            try {
                IDbmsModule analyser = null;

                switch (DbSystemEnum) {
                case DbSystemEnum.Mysql:
                    //analyser = new MysqlAnalyser(this);
                    break;
                case DbSystemEnum.Mssql:
                    analyser = MssqlModule.Instance;
                    break;
                case DbSystemEnum.Msaccess:
                    //analyser = new MsaccessAnalyser(this);
                    break;
                case DbSystemEnum.Pgsql:
                    //analyser = new PgsqlAnalyser(this);
                    break;
                case DbSystemEnum.Sqlite:
                    analyser = SqliteModule.Instance;
                    break;
                }

                analyser.AnalyseDatabase(this);
                AddLog("Database analysed successfully.");
                RaisePropertyChanged(() => Tables);
                RaisePropertyChanged(() => Views);
                RaisePropertyChanged(() => Procedures);
            }
            catch (Exception ex) {
                StringBuilder s = new StringBuilder(10000);
                s.AppendLine("Unable to analyse the database:");
                s.AppendLine();
                s.AppendLine("Message:");
                s.AppendLine(ex.Message);
                s.AppendLine();
                s.AppendLine("StackTrace:");
                s.AppendLine(ex.StackTrace);
                Dialogs.ShowError(s.ToString());
                AddLog("Unable to analyse the database: " + ex.Message);
            }
        }
        bool CanAnalyse()
        {
            return DbSystemEnum == DbSystemEnum.Sqlite || DbSystemEnum == DbSystemEnum.Mssql;
        }

        void Generate()
        {
            try {
                IDbmsModule codeGenerator = null;

                switch (DbSystemEnum) {
                case DbSystemEnum.Mysql:
                    //codeGenerator = new MysqlCodeGenerator(this);
                    break;
                case DbSystemEnum.Mssql:
                    codeGenerator = MssqlModule.Instance;
                    break;
                case DbSystemEnum.Msaccess:
                    //codeGenerator = new MsaccessCodeGenerator(this);
                    break;
                case DbSystemEnum.Pgsql:
                    //codeGenerator = new PgsqlCodeGenerator(this);
                    break;
                case DbSystemEnum.Sqlite:
                    codeGenerator = SqliteModule.Instance;
                    break;
                }

                // generate sources
                Dictionary<string, string> sources = codeGenerator.GenerateCode(this);

                // write sources
                DirectoryInfo directoryOutput = new DirectoryInfo(OutputDirectory);
                if (!directoryOutput.Exists)
                    directoryOutput.Create();
                foreach (string fileToSave in sources.Keys)
                    File.WriteAllText(Path.Combine(OutputDirectory, fileToSave), sources[fileToSave]);
                
                // compile into assembly
                if (Settings.Default.BuildAssembly) {
                    var compilerResults = CodeCompiler.CompileCode(
                        sources.Values.Concat(new[] { Settings.Default.AssemblyInfo }).ToArray(),
                        codeGenerator.AdditionalReferencedAssemblies,
                        Path.Combine(OutputDirectory, Namespace + ".dll"),
                        Settings.Default.GenerateXmlDoc ? Path.Combine(OutputDirectory, Namespace + ".xml") : null);
                    Debug.WriteLine(compilerResults.Errors.Count);

                    if (compilerResults.Errors.Count > 0)
                        throw new Exception("Failed to build assembly; got " + compilerResults.Errors.Count + " compilation errors");
                }

                AddLog("Sources generated successfully.");
                Process.Start(OutputDirectory);
            }
            catch (Exception ex) {
                Dialogs.ShowError("Unable to generate the sources: " + ex.Message);
                AddLog("Unable to generate the sources: " + ex.Message);
            }
        }
        bool CanGenerate()
        {
            return DbSystemEnum == DbSystemEnum.Sqlite || DbSystemEnum == DbSystemEnum.Mssql;
        }

        void AddLog(string str)
        {
            str = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " | " + str;
            Child_Logs.Add(new Models.Log.Row(0, 0, str));
        }

        public IEnumerable<DatabaseItem.Row> Tables
        {
            get { return Child_DatabaseItems.Where(_ => _.Type == (int)DatabaseItemTypeEnum.Table); }
        }

        public IEnumerable<DatabaseItem.Row> Views
        {
            get { return Child_DatabaseItems.Where(_ => _.Type == (int)DatabaseItemTypeEnum.View); }
        }

        public IEnumerable<DatabaseItem.Row> Procedures
        {
            get { return Child_DatabaseItems.Where(_ => _.Type == (int)DatabaseItemTypeEnum.Procedure); }
        }

        public bool IsItemDuplicate(string qualifiedNameSanitized)
        {
            return Child_DatabaseItems.Where(i => i.QualifiedNameSanitized == qualifiedNameSanitized).Count() > 1;
        }

        public void AddTable(string schema, string name)
        {
            DatabaseItem.Row row = new DatabaseItem.Row(0, 0, schema, name, (int)DatabaseItemTypeEnum.Table, true, true, false);
            Child_DatabaseItems.Add(row);
        }
        public void AddView(string schema, string name, bool isReadOnly)
        {
            DatabaseItem.Row row = new DatabaseItem.Row(0, 0, schema, name, (int)DatabaseItemTypeEnum.View, true, true, isReadOnly);
            Child_DatabaseItems.Add(row);
        }
        public void AddProcedure(string schema, string name)
        {
            DatabaseItem.Row row = new DatabaseItem.Row(0, 0, schema, name, (int)DatabaseItemTypeEnum.Procedure, true, true, true);
            Child_DatabaseItems.Add(row);
        }
    }
}
